package cpoo.video;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.media.Buffer;
import javax.media.Format;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import javax.media.util.BufferToImage;

import cpoo.exceptions.MotionException;
import cpoo.globalMotion.FrameComparator;
import cpoo.util.Loger;

public class GlobalMotionCodec extends PreAccessCodec {
	
	private Dimension size;
	private File output;
	private FrameComparator fc;
	private Loger tLogger;
	private Loger vectorLog;
	private int numberOfDroppedExtremes;
	private boolean showBlockasMovement;
	private double maxDiffPercentage = Double.MAX_VALUE; 
	private int skipFrames = 0;
	private int framesSkipped = 0;
	private boolean isFirst = true;
	ArrayList<AffineTransform> lastTransforms;
	int transformsLength;
	int actualTransformsLength;
	public GlobalMotionCodec(Dimension size, File output, Rectangle cr, int crRows, int crCols, int numberOfDroppedExtremes, boolean showBlocksMovement, double maxPercentageDifference, int meanTransformSeriesLength, File log, File vectorLogFile) {
		supportedIns = new Format[] { new RGBFormat()};
		this.size = size;
		this.output = output;
		
		fc = new FrameComparator(cr, crCols, crRows);
		this.numberOfDroppedExtremes = numberOfDroppedExtremes;
		this.showBlockasMovement =showBlocksMovement;
		this.maxDiffPercentage = maxPercentageDifference;
		lastTransforms = new ArrayList<AffineTransform>(meanTransformSeriesLength);
		transformsLength = meanTransformSeriesLength;
		actualTransformsLength = 0;
		
		tLogger = new Loger(log);
		tLogger.clearLog();
		
		vectorLog = new Loger(vectorLogFile);
		vectorLog.clearLog();
	}
	
	public String getName() {
		return "Global Motion Codec";
	}
	
	
	protected Image accessFrame(Buffer frame) {
		 
		BufferedImage outImage = null;
		
		BufferToImage stopBuffer = new BufferToImage((VideoFormat) frame.getFormat());
		Image stopImage = stopBuffer.createImage(frame);
		try {
			outImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
			Graphics og = outImage.getGraphics();
			og.drawImage(stopImage, 0, 0, size.width, size.height, null);
			fc.setCompareRegion(new Rectangle(0,0,outImage.getWidth(), outImage.getHeight()));
			if (isFirst || framesSkipped == skipFrames){
				isFirst = false;
				framesSkipped = 0;
				
				fc.pushFrame(outImage);
				Point[] po = fc.getOriginalPoints();
				Point[] pt = fc.getTransformedPoints(true, true);
				if (pt != null){
					AffineTransform tr = fc.findTransformation(po, pt, numberOfDroppedExtremes);
					if (actualTransformsLength < transformsLength){
						lastTransforms.add(tr);
						actualTransformsLength ++;
					} else {
						AffineTransform mean = meanTransform();
						AffineTransform toAdd = tr;
						if (differsALot(mean, tr)){
							System.out.println("BIG DIFFERENCE IN TRANSFORMATION");
							tr = mean;
							toAdd = tr;
						}
						lastTransforms.remove(0);
						lastTransforms.add(toAdd);
					}
					System.out.println("transformacja: "+tr);
					double[] matrix = new double[6];
					tr.getMatrix(matrix);
					tLogger.log(matrix);
					
					/*for (int i=0; i < po.length; i++){ 
						try { 
							tr.inverseTransform(po[i], po[i]); 
						} catch (NoninvertibleTransformException e){ 
							e.printStackTrace(); 
						} 
					}*/
					
					vectorLog.saveVectors(po, pt);
				}
				if (output != null){
					//prepareImage(outImage,rheight,rheight, null);
					Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
					ImageWriter writer = (ImageWriter) writers.next();
	
					//Once an ImageWriter has been obtained, its destination must be set to an ImageOutputStream: 
					File f = new File(output.getAbsolutePath()+"/"+frame.getSequenceNumber() + ".jpg");
					ImageOutputStream ios = ImageIO.createImageOutputStream(f);
					writer.setOutput(ios);
	
					//Finally, the image may be written to the output stream: 
					//BufferedImage bi;
					//writer.write(imagebi);
					writer.write(outImage);
					ios.close();
				}
				if (showBlockasMovement){
					paintMovement(outImage, po, pt);
				}
			}else {
				framesSkipped ++;
			}
			
		} catch (IOException e) {
			System.out.println("Error :" + e);
		} catch (MotionException e) {
			e.printInfo();
		}
		
		long t = (long) (frame.getTimeStamp() / 10000000f);
		System.err.println(
			"Post: frame #: "
				+ frame.getSequenceNumber()
				+ ", time: "
				+ ((float) t) / 100f
				+ ", len: "
				+ frame.getLength());
		return outImage;
	}
	
	private AffineTransform meanTransform(){
		AffineTransform t = new AffineTransform();
		
		double tx = 0.0;
		double ty = 0.0;
		
		for (int i=0; i<lastTransforms.size(); i++){
			tx += lastTransforms.get(i).getTranslateX();
			ty += lastTransforms.get(i).getTranslateY();
		}
		t.translate(tx/lastTransforms.size(), ty/lastTransforms.size());
		return t;
	}
	
	private void paintMovement(BufferedImage image, Point[] original, Point[] transformed){
		if (original == null || transformed == null) return;
		Graphics g = image.getGraphics();
		g.setColor(Color.WHITE);
		for (int i = 0; i<original.length; i++){
			Point o = original[i];
			Point t = transformed[i];
			
			g.drawLine(o.x, o.y, t.x, t.y);
		}
	}
	
	private boolean differsALot(AffineTransform t1, AffineTransform t2){
		double xDiff = Math.abs( t1.getTranslateX() - t2.getTranslateX());
		double yDiff = Math.abs( t1.getTranslateY() - t2.getTranslateY());
//		if (t1.getTranslateX() == 0 || t1.getTranslateY() == 0){
//			return false;
//		}
		if (xDiff > maxDiffPercentage || yDiff > maxDiffPercentage){
			System.out.println("XDIFF = "+xDiff+" YDIFF = "+yDiff);
			return true;
		}
		return false;
	}
}
